﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

/*
 * ###################
 * Player Interface:
 * .playerAction()
 * .joinTable() // joining as spec
 * .partTable() // parting from table
 * .SeatDown // joining to seat as player
 * .PlaceOutSeat // parting from seat
 * .playerAction()
 * ###################
 * 
 * 
 */

using ConnectionControl;

namespace pokerServer.GameManagment.GameTools {

	public class Table {

		public static Boolean ONLY_RESPECTED_PLAYERS_CAN_VIEW = false; // whoevers got the threshold chips can view.
		public static int TIME_BETWEEN_GAMES = 5000;

		public enum TableStates { WaitingForPlayers, GameOn }

		private uint tableID;
		private string tableName;
		private uint buyInMin;
		private uint buyInMax;
		private uint stakeMin;
		private uint stakeMax;
		private ushort playersMin;
		private ushort playersMax;


		public ConnectionManag cm;
		public Boolean TableRunning = false;
		public TableStates tableState;
		public List<Player> spectatorList;
		public Seat[] playerList;
		public Exception startException = null;
		//public ushort numberOfSeatingPlayers = 0;

		// Game vars
		public CardsDeck Cards = null;
		public Card[] gameTableCards = null;
		public Player[] gameWinners = null;
		public Player[] gameFinalists = null;

		public GameEngine gameEngine;
		public ushort gameIndexDealer;
		public ushort gameIndexSmall;
		public ushort gameIndexBig;
		public ushort gameIndexCurrent;
		private uint gamePotSize = 0;
		// round vars
		private uint gamePotSizeRound;
		private GameEngine.GameDecisions gameLastAction;
		private ushort gameLastRaiseIndex;
		private uint gameLastRaiseValue;
		public uint gameRaiseRoundAmount;

		public Table(
			ConnectionManag cm,
			uint tableID, uint buyInMin, uint buyInMax, uint stakeMin, uint stakeMax/*, printToLog console*/,
			ushort playersMin = 2, ushort playersMax = 9
			) {

			this.tableID = tableID;
			this.buyInMax = buyInMax;
			this.buyInMin = buyInMin;
			this.stakeMax = stakeMax;
			this.stakeMin = stakeMin;
			this.playersMin = playersMin;
			this.playersMax = playersMax;
			this.cm = cm;

			this.init();
		}
		private void init() {
			this.playerList = new Seat[playersMax];
			this.spectatorList = new List<Player>();
			//this.gameEngine = new GameEngine(this);
			//this.numberOfSeatingPlayers = 0;
			this.gameIndexDealer = 100;
			this.gameIndexCurrent = 100;
			this.TableState = TableStates.WaitingForPlayers;
		}
		private void reset() {

			Cards = new CardsDeck();
			this.gameWinners = null;
			this.gameFinalists = null;
			this.FullTableCards = null;
			this.PotSize = 0;
			this.gamePotSizeRound = 0;
		}
		public void resetPhaseVars() {
			this.PotSize += this.PotSizeRound;
			this.gamePotSizeRound = 0;
			this.gameRaiseRoundAmount = 0;
			this.LastRaiseIndex = 100;
			this.LastAction = GameEngine.GameDecisions.Check;
		}
		public TableStates TableState {
			get {
				return this.tableState;
			}
			set {
				this.tableState = value;
			}
		}

		public virtual uint TableID {
			get {
				return tableID;
			}
		}
		public virtual uint BuyInMin {
			get {
				return this.buyInMin;
			}
		}
		public virtual uint BuyInMax {
			get {
				return this.buyInMax;
			}
		}
		public virtual uint StakeMin {
			get {
				return this.stakeMin;
			}
		}
		public virtual uint StakeMax {
			get {
				return this.stakeMax;
			}
		}
		public virtual ushort PlayersMin {
			get {
				return this.playersMin;
			}
		}
		public virtual ushort PlayersMax {
			get {
				return this.playersMax;
			}
		}
		public virtual uint PotSize {
			get {
				return this.gamePotSize + this.gamePotSizeRound;
			}
			set {
				this.gamePotSize = value;
			}
		}
		public void addToPot(uint value) {
			this.gamePotSizeRound += value;
		}
		public virtual uint PotSizeRound {
			get {
				return this.gamePotSizeRound;
			}
		}
		public GameEngine.GameDecisions LastAction {
			get {
				return this.gameLastAction;
			}
			set {
				this.gameLastAction = value;
			}
		}
		public ushort LastRaiseIndex {
			get {
				return this.gameLastRaiseIndex;
			}
			set {
				this.gameLastRaiseIndex = value;
			}
		}
		public uint LastRaiseValue {
			get {
				return this.gameLastRaiseValue;
			}
			set {
				this.gameLastRaiseValue = value;
			}
		}
		public GameEngine.GameStates GameState {
			get {
				if (this.gameEngine == null) {
					return GameEngine.GameStates.NotStarted;
				}
				return this.gameEngine.GameState;
			}
			set {
				this.gameEngine.GameState = value;
			}
		}

		public ushort IndexPlayerDealer {
			get {
				return this.gameIndexDealer;
			}
			set {
				this.gameIndexDealer = value;
			}
		}
		public ushort IndexPlayerSmall {
			get {
				return this.gameIndexSmall;
			}
		}
		public ushort IndexPlayerBig {
			get {
				return this.gameIndexBig;
			}
		}
		public ushort IndexPlayerCurrent {
			get {
				return this.gameIndexCurrent;
			}
			set {
				this.gameIndexCurrent = value;
			}
		}
		public Seat SeatDealer {
			get {
				return this.playerList[this.IndexPlayerDealer];
			}
		}
		public Seat SeatSmall {
			get {
				return this.playerList[this.IndexPlayerSmall];
			}
		}
		public Seat SeatBig {
			get {
				return this.playerList[this.IndexPlayerBig];
			}
		}
		public Seat SeatCurrent {
			get {
				return this.playerList[this.IndexPlayerCurrent];
			}
		}

		public ushort NumberOfSeatingPlayers {
			get {
				ushort num = 0;
				foreach (Seat s in this.playerList) {
					if (s != null) {
						num++;
					}
				}
				return num;
			}
		}
		public ushort NumberOfPlayingPlayers {
			get {
				return (ushort)(this.gameEngine.playerList.Count);
			}
		}

		public ushort lookFirstTakenSeat(ushort index) {
			for (ushort i = (ushort)(index % this.playersMax); i != index; i = (ushort)((i + 1) % this.playersMax)) {
				if (this.playerList[i] == null) {
					continue;
				}
				return i;
			}
			return index;
		}
		public ushort nextTakenSeat(ushort seatID) {
			for (ushort i = seatID; i != this.playerList.Length % this.playersMax; i++) {
				if (this.playerList[i % this.playersMax] == null) {
					continue;
				}
				return (ushort)(i % this.playersMax);
			}
			return seatID;
		}
		public ushort nextPlayingPlayerIndex(ushort seatid) {
			int start = this.IndexPlayerCurrent;
			for (int i = (start + 1) % this.PlayersMax; i != start; i = (i + 1) % this.PlayersMax) {
				if (this.playerList[i] != null && this.playerList[i].isPlaying() && this.playerList[i].Player.BuyIn > 0) {
					return (ushort)i;
				}
			}
			return seatid;
		}

		public void TableWorker() {

			this.TableRunning = true;
			while (this.TableRunning) {
				try {
					if (this.GameState == GameEngine.GameStates.NotStarted || this.GameState == GameEngine.GameStates.EndGame) {
						Console.WriteLine("Trying to Start Thread for: " + this);
						Thread t = new Thread(new ThreadStart(this.startGame));
						t.IsBackground = true;

						t.Start();
					}
				}
				catch (Exception e) {
					Console.WriteLine("TableWorker Exception: {0}", e.Message);
				}
				if (this.startException != null) {
					Console.WriteLine("startGameException: {0}", this.startException);
				}
				Console.WriteLine("Sleeping TableWorker: " + this);
				Thread.Sleep(7000);
			}
			Console.WriteLine("Exit TableWorker: " + this);
		}
		public void startGame() {

			if (this.GameState != GameEngine.GameStates.NotStarted && this.GameState != GameEngine.GameStates.EndGame) {
				startException = new GameIsAlreadyStartedGameEngineException();
			}
			else if (this.NumberOfSeatingPlayers < this.PlayersMin) {
				startException = new MinimumPlayersGameEngineException("Not reached minimum players to start a game.");
			}
			else {
				startException = null;
				this.reset();
				this.gameEngine = new GameEngine(this);
				this.gameEngine.startGame();
			}
		}

		public void endGame() {
			
			this.sendAllPlayers(MessagesProtocol.EndRoundProtocol(this.GameState));
			this.calculateWinners();
			this.gameEngine.printFullStatus();
			this.rewardWinners();
			this.sendAllPlayers(MessagesProtocol.ShowHandsProtocol(this.gameFinalists));
			this.sendAllPlayers(MessagesProtocol.WinnerProtocol(this.gameWinners));
			this.TableState = TableStates.WaitingForPlayers;

			Thread.Sleep(15000);
			this.GameState = GameEngine.GameStates.EndGame;
			//Thread.Sleep(TIME_BETWEEN_GAMES);
			// if () launch another game.. mybe using deamon
		}



		public Boolean isValidBuyIn(uint value) {
			return (value >= this.BuyInMin && value <= this.BuyInMax);
		}
		public Boolean canJoin(Player p) {
			if (p.Chips < this.buyInMin)
				return false;
			return true;
		}


		public void publishPlayerList(Player p) {
			this.sendPlayer(p, MessagesProtocol.PlayerListProtocol(this.TableID, this.playerList));
		}
		public void publishPlayerList() {
			this.sendAllPlayers(MessagesProtocol.PlayerListProtocol(this.TableID, this.playerList));
		}
		public void publishTableInfo(Player p) {
			this.sendPlayer(p, this.strTableInfo());
		}
		public void publishChipTable(Player p) {
			this.sendPlayer(p, MessagesProtocol.ChipTableProtocol(this.TableID, this.playerList));
		}
		public void publishChipTable() {
			this.sendAllPlayers(MessagesProtocol.ChipTableProtocol(this.TableID, this.playerList));
		}
		public void publishCurrentGame(Player p) {
			//{1} {2} {3} {4}
			//Console.WriteLine("TESTttttttttt: {0} {1} {2} {3}", GameEngine.GAMES_PLAYED, this.PotSize, this, this.TableCards, this.IndexPlayerCurrent);
			this.sendPlayer(p, MessagesProtocol.CurrentGameProtocol(GameEngine.GAMES_PLAYED, this.PotSize, this, this.TableCards, this.IndexPlayerCurrent));
		}
		public void publishCurrentGame() {
			this.sendAllPlayers(MessagesProtocol.CurrentGameProtocol(GameEngine.GAMES_PLAYED, this.PotSize, this, this.TableCards, this.IndexPlayerCurrent));
		}



		// joining player to a table, and by doing so, signning him to receive information about the current game.
		public void joinTable(Player p) {
			if (Table.ONLY_RESPECTED_PLAYERS_CAN_VIEW && !this.canJoin(p)) {
				throw new NotEnoughtValueTableException();
			}

			this.spectatorList.Add(p);

			this.sendPlayer(p, "JOINTABLE-ACK " + this.TableID);
			this.publishTableInfo(p);
			this.publishPlayerList(p);
			this.publishCurrentGame(p);
		}
		// guess what this method does ?!
		public void partTable(Player p) {
			if (p == null) {
				throw new PlayerIsNullPlayerException();
			}
			else if (this.gameEngine != null) {

				this.gameEngine.evtPlayerQuitOrRanOutOfTime(p);
				this.PlaceOutSeat(p.Seat.SeatID);

				if (this.gameEngine.playerList.Count <= 1) {
					this.endGame();
				}
				//this.sendPlayer(p, "PARTTABLE-ACK " + this.TableID);
			}
		}



		private void PlacePlayerAtSeat(Player p, UInt16 seatID) {
			//this.numberOfSeatingPlayers++;
			Seat s = new Seat(this, p, seatID);
			this.playerList[seatID] = s;
			p.Seat = s;

		}
		public void PlaceOutSeat(Seat s) {
			this.PlaceOutSeat(s.SeatID);
		}
		public void PlaceOutSeat(ushort seatID) {
			Seat s = this.playerList[seatID];
			Player p = s.Player;
			this.playerList[seatID] = null;
			//this.numberOfSeatingPlayers--;
			//this.sendAllPlayers(MessagesProtocol.PartedTableProtocol(s));
			p.SeatUP();
			this.spectatorList.Add(p);

			this.sendPlayer(p, "SEATUPTABLE-ACK " + this.TableID);
			this.publishPlayerList();
		}
		public void SeatDown(Player p, ushort seatID, uint buyIn) {
			lock (this) {
				Console.WriteLine("Table --->" + this);

				if (seatID >= this.playersMax) {
					throw new SeatIdNotExists();
				}
				else if (this.playerList[seatID] != null) {
					throw new SeatIsTakenException();
				}
				else if (!this.isValidBuyIn(buyIn)) {
					throw new NotValidBuyInValueTableException();
				}
				else if (!p.canBuyIn(buyIn)) {
					throw new NotEnoughtValueTableException();
				}

				this.PlacePlayerAtSeat(p, seatID);
				this.spectatorList.Remove(p);
				Seat s = this.playerList[seatID];
				p.SeatDown(this, s, buyIn);

				this.sendPlayer(p, string.Format("SEATDOWNTABLE-ACK {0} {1} {2} {3}", this.TableID, s.SeatID, p.BuyIn, p.PlayerName));
				this.publishPlayerList();
				//evtPlayerJoinedTable(SeatID, p);
			}
		}

		// main method
		// when receiving players data response from the receive socket handler, it will launch this method for appropriate actions.
		public void playerAction(Player p, GameEngine.GameDecisions gDecision = GameEngine.GameDecisions.NoChoise, uint value = 0) {
			this.playerAction(p.Seat, gDecision, value);
		}
		public void playerAction(Seat seat, GameEngine.GameDecisions gDecision = GameEngine.GameDecisions.NoChoise, uint value = 0) {

			if (gameEngine.GameState < GameEngine.GameStates.PreFlop || gameEngine.GameState == GameEngine.GameStates.EndGame) {
				throw new GameNotStartedGameEngineException();
			}
			else if (this.IndexPlayerCurrent != seat.SeatID) {
				seat.Player.sendData(MessagesProtocol.ERR_Exception(new PlayerTurnGameEngineException()));
				throw new PlayerTurnGameEngineException();
			}

			// sending gameEngine the players action.
			gameEngine.receivedPlayerAction(seat, gDecision, value);
		}

		public void parseData(Player p, string data) {

			string[] words = data.Trim().ToUpper().Split(' ');

			switch (words[0]) {
				case "JOINTABLE":
					this.joinTable(p);
					break;

				case "PARTTABLE":
					this.partTable(p);
					break;

				case "SEATDOWNTABLE":
					this.SeatDown(p, Convert.ToUInt16(words[2]), Convert.ToUInt32(words[3]));
					break;
				case "SEATUPTABLE":
					this.PlaceOutSeat(p.Seat);
					break;

				case "ACTION":

					switch (words[1]) {
						case "FOLD":
							this.playerAction(p.Seat, GameEngine.GameDecisions.Fold);
							break;

						case "CHECK":
							this.playerAction(p.Seat, GameEngine.GameDecisions.Check);
							break;

						case "CALL":
							this.playerAction(p.Seat, GameEngine.GameDecisions.Call);
							break;

						case "RAISE":
							this.playerAction(p.Seat, GameEngine.GameDecisions.Raise, Convert.ToUInt32(words[2]));
							break;
					}
					break;
				default:
					break;
			}

		}
		public void removePlayer(Player player) {

			foreach (Seat s in this.playerList) {
				if (s == null) {
					continue;
				}

				if (s.Player == player) {
					if (player.isPlaying) {
						try {
							this.PlaceOutSeat(s);
						}
						catch {
						}
					}
					this.publishPlayerList();
				}
			}

			foreach (Player p in this.spectatorList) {
				if (p == null) {
					continue;
				}
				if (player == p) {
					this.spectatorList.Remove(player);
				}
			}

			
		}

		public void sendPlayer(Player p, string data) {

			Console.WriteLine("Server -> Client[{0}] Data: {1}", p, data);
			//p.sendData(data);
			p.cc.SendMsg(data);
		}
		public void sendAllPlayers(string data) {
			Console.WriteLine("Server: Sending AllPlayers. [{0}]", data);
			foreach (Seat s in this.playerList) {
				if (s == null) {
					continue;
				}
				this.sendPlayer(s.Player, data);
				//Console.WriteLine("Server -> Client[{0}] Data: {1}", s.Player, data);
			}
			foreach (Player p in this.spectatorList) {
				this.sendPlayer(p, data);
			}
			Console.WriteLine("Server: End Sending AllPlayers.", data);
		}
		public void sendPlayingPlayers(string data) {
			Console.WriteLine("Server: Sending PlayingPlayers. [{0}]", data);
			foreach (Seat s in this.gameEngine.playerList) {
				this.sendPlayer(s.Player, data);
			}
			Console.WriteLine("Server: End Sending PlayingPlayers.", data);
		}

		public string strTableInfo() {
			return MessagesProtocol.TableInfoProtocol(this.TableID, this.BuyInMin, this.BuyInMax, this.StakeMin, this.StakeMax, this.PlayersMin, this.PlayersMax, this.NumberOfSeatingPlayers);
		}

		public virtual Card[] TableCards { // can improve largly by saving the current amount of cards
			get {
				if (this.gameEngine == null) {
					return null;
				}
				List<Card> tempCards = new List<Card>();
				ushort k;
				switch (this.GameState) {
					case GameEngine.GameStates.Flop:
						k = 3;
						break;
					case GameEngine.GameStates.Turn:
						k = 4;
						break;
					case GameEngine.GameStates.River:
					case GameEngine.GameStates.ShowHands:
					case GameEngine.GameStates.EndGame:
						k = 5;
						break;

					default:
						k = 0;
						break;
				}
				for (ushort i = 0; i < k; i++) {
					tempCards.Add(this.FullTableCards[i]);
				}
				return tempCards.ToArray<Card>();
			}
		}
		public virtual Card[] FullTableCards {
			get {
				return this.gameTableCards;
			}
			set {
				this.gameTableCards = value;
			}
		}
		public void calculateWinners() {
			this.gameFinalists = this.SeatAsPlayingPlayers;
			this.WhoWon(this.gameFinalists);
		}
		public void rewardWinners() {
			// to handle splits
			if (this.gameWinners.Length > 0) {
				double split = (double)(this.PotSize) / (double)(gameWinners.Length);
				Console.WriteLine("Distributing Earnning:");
				foreach (Player p in this.gameWinners) {
					if (p == null) {
						continue;
					}
					p.wonChips(((uint)split));
				}
				Console.WriteLine("END Distributing Earnning");
			}
		}


		public virtual void printTableCards() {
			//Tools.MyFunc.printCardARR(this.TableCards);
		}
		public virtual string STableCards {
			get {
				return "Flop: " + this.CFlop + "\tTurn:  " + this.CTurn + "\tRiver: " + this.CRiver;
			}
		}
		// Pull and Deal cards
		public virtual void DealCards() {
			for (int i = 0; i < this.gameEngine.playerList.Count && this.gameEngine.playerList[i] != null; i++) {
				this.gameEngine.playerList[i].Player.setCards(Cards.DrawCard, Cards.DrawCard);
			}
		}
		public virtual void pullFlop() {
			this.GameState = GameEngine.GameStates.Flop;
			for (int i = 0; i < 3; i++) {
				gameTableCards[i] = Cards.DrawCard;
			}
		}
		public virtual void pullTurn() {
			this.GameState = GameEngine.GameStates.River;
			gameTableCards[3] = Cards.DrawCard;
		}
		public virtual void pullRiver() {
			this.GameState = GameEngine.GameStates.River;
			gameTableCards[4] = Cards.DrawCard;
		}
		public virtual string CFlop {
			get {
				string str = "";
				for (int i = 0; i < 3; i++) {
					str += gameTableCards[i] + "\t";
				}
				return str;
			}
		}
		public virtual Card CTurn {
			get {
				return gameTableCards[3];
			}
		}
		public virtual Card CRiver {
			get {
				return gameTableCards[4];
			}
		}


		public Player[] SeatAsPlayers {
			get {
				List<Player> pList = new List<Player>();
				foreach (Seat s in this.playerList) {
					pList.Add(s.Player);
				}
				return pList.ToArray();
			}
		}
		public Player[] SeatAsPlayingPlayers {
			get {
				List<Player> pList = new List<Player>();
				foreach (Seat s in this.gameEngine.playerList) {
					if (s != null && s.Player.isPlaying) {
						pList.Add(s.Player);
					}
				}
				return pList.ToArray();
			}
		}

		public virtual void WhoWon(Player[] pList) {

			Player[] pListArray = new Player[1];
			int mHand = 0, mNumWons = 0, tHand = 0;
			for (int i = 0; i < pList.Length; i++) {

				if (pList[i] == null /*|| pList[i].LastAction != GameEngine.GameDecisions.Fold*/) {
					continue;
				}

				tHand = pList[i].Hand();
				if (tHand > mHand) {
					pListArray = new Player[this.gameEngine.playerList.Count - i]; // too check if it is the correct folrmula. -eff TO CHANGE TO NUMBER OF ACTULLY PLAYING
					mHand = tHand;
					mNumWons = 0;
				}
				if (tHand == mHand) {
					pListArray[mNumWons] = pList[i];
					mNumWons++;
				}
			}
			Player[] pWin = null;
			Player p = null;
			int cc = 0;
			for (int i = 0, z; i < mNumWons; i++) {
				if (i > 0) {
					z = p.CompareSimilarHands(pListArray[i].nabestHand);
				}
				else {
					z = 2;
				}

				if (z == 0) {
					pWin[cc++] = pListArray[i];
				}
				else if (z == 2) {
					pWin = new Player[mNumWons - i]; // too check if mNumWons-i is the correct formula. -eff
					pWin[0] = pListArray[i];
					p = pWin[0];
					cc = 1;
				}
			}
			mNumWons = cc;
			this.gameWinners = new Player[mNumWons];
			Console.WriteLine("Num of Players " + mNumWons + "\tWinning hand is: " + mHand + "[" + Player.replaceHandToString(mHand) + "]");
			for (int i = 0; i < cc; i++) {
				if (pWin[i] == null) {
					continue;
				}
				this.gameWinners[i] = pWin[i];
				Console.WriteLine("Winnger: " + pWin[i]);
			}
			this.gameWinners = pWin;
		}


		public override string ToString() {
			return string.Format("Table#{0} BuyIn[{1}/{2}] Stakes[{3}/{4}] Players[{5}/({6},{7}) PotSize: {8}/PotSizeRound: {9}",
									this.TableID, this.BuyInMin, this.BuyInMax, this.StakeMin, this.StakeMax, this.NumberOfSeatingPlayers, this.PlayersMin, this.PlayersMax, this.PotSize, this.PotSizeRound);
		}
		public void printSeats() {
			foreach (Seat s in this.playerList) {

			}
		}
		public void printPlayers() {
			Console.WriteLine("Printing table player list.");
			foreach (Seat s in this.playerList) {
				if (s != null) {
					writeToLog(string.Format("{0}\t", s.Player));
				}
			}
		}

		public void writeToLog(string buffer) {
			Console.WriteLine(buffer);
			//this.myWin.printToLog(buffer);

			//console.Invoke(buffer + '\n');
		}
	}

	// Table Exceptions
	public abstract class TableException : Exception {
		public TableException() { }
		public TableException(string message) : base(message) { }
	}
	public class TableIsntExistsTableException : TableException { }
	public class FullTableException : TableException { }
	public class PrivateTableException : TableException { }
	public class NotCreatedTableException : TableException { }
	public class NotEnoughtValueTableException : TableException { }
	public class NotValidBuyInValueTableException : TableException { }

}